/*
 * Copyright 2020-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package work.mediway.web.configure;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import com.mediway.hos.app.common.enums.PunctuationEnum;
import com.mediway.hos.app.core.config.seure.HosAppTokenHandler;
import com.mediway.hos.app.core.filter.HosCoreFilter;
import com.mediway.hos.app.core.filter.HosPostFilter;
import com.mediway.hos.app.core.filter.HosPostInfoService;
import com.mediway.hos.app.core.property.FrameworkSecurity;
import com.mediway.hos.app.core.util.NoneRequestMatcher;
import com.mediway.hos.common.util.StringUtil;
import com.mediway.hos.common.util.context.SpringContextUtils;
import com.mediway.hos.i18n.constant.I18nConstant;
import com.mediway.hos.i18n.service.I18nStaticTranslationService;
import com.mediway.hos.i18n.utils.HosI18nUtil;
import com.mediway.hos.i18n.utils.I18nUtil;
import com.mediway.hos.manage.loginextend.converter.CallBackAuthenticationConverter;
import com.mediway.hos.manage.loginextend.converter.CasAuthenticationConverter;
import com.mediway.hos.manage.loginextend.provider.CallBackAuthenticationProvider;
import com.mediway.hos.manage.loginextend.provider.CasAuthenticationProvider;
import com.mediway.hos.oauth.properties.AuthProperties;
import com.mediway.hos.security.core.CommonAuthenticationHandler;
import com.mediway.hos.security.core.util.SecurityUtil;
import com.mediway.hos.security.filter.XssFilter;
import com.mediway.hos.security.login.HosSecurityLoginDatabaseAutoConfiguration;
import com.mediway.hos.security.login.HosUserDetailsService;
import com.mediway.hos.security.login.configurers.HosConfigurerUtils;
import com.mediway.hos.security.login.configurers.HosLoginConfigurer;
import com.mediway.hos.security.login.enums.LoginAccountEnum;
import com.mediway.hos.security.login.mapper.HosBaseUserMapper;
import com.mediway.hos.security.login.service.HosUserDetailsDBService;
import com.mediway.hos.security.login.service.LoginModelService;
import com.mediway.hos.security.login.token.HosTokenAuthenticationTokenGenerator;
import com.mediway.hos.security.properties.HosSecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author Joe Grandja
 * @since 0.0.1
 */
@EnableWebSecurity
@Configuration
@AutoConfigureAfter({HosSecurityLoginDatabaseAutoConfiguration.class})
@EnableConfigurationProperties(FrameworkSecurity.class)
public class SecurityLoginConfig {
    @Value("${sys.backendPath}")
    private String backendPath;
    @Autowired
    private AuthProperties authProperties;
    @Value("${server.servlet.context-path}")
    private String contextPath;

    @Value("${sys.restfulPath}")
    private String restfulPath;
    @Autowired
    private FrameworkSecurity frameworkSecurity;
    /** 开启防跨站脚本攻击，默认为否 */
    @Value("${framework.security.xss.enable:false}")
    private boolean xssEnable;

    /** xss过滤的排除链接 */
    @Value("${framework.security.xss.excludes:}")
    private List<String> xssExcludes;

    /** 开启防跨站请求伪造，默认为否 */
    @Value("${framework.security.csrf.enable:false}")
    private boolean csrfEnable;

    /** 开启跨域，默认为否 */
    @Value("${framework.security.cors.enable:false}")
    private boolean corsEnable;
    @Autowired
    private HosBaseUserMapper hosUserMapper;

    @Autowired
    private LoginModelService loginModelService;
    @Autowired
    private HosSecurityProperties hosSecurityProperties;
    /**
     * 登录模块错误码国际化翻译
     */
    static {
        CommonAuthenticationHandler.setBaseResponseConsumer(baseResponse -> {
            if (StringUtil.isBlank(baseResponse.getCode()) || "200".equals(baseResponse.getCode())) {
                return;
            }
            if (HosI18nUtil.isOpenI18n()) {
                I18nStaticTranslationService i18nStaticTranslationService = SpringContextUtils.getBean(I18nStaticTranslationService.class);
                String defaultLanguage = I18nConstant.I18N_DEFAULT_LANGUAGE;
                String language = HosI18nUtil.getRequestLanguage();
                String msg = i18nStaticTranslationService.queryTransByResponseCode(baseResponse.getCode(), baseResponse.getMsg(), defaultLanguage, language);
                if (StringUtil.isNotBlank(msg)) {
                    baseResponse.setMsg(msg);
                }
            }
        });
        LoginAccountEnum.function = keys -> {
            List<String> values = new ArrayList<>();
            keys.forEach(key -> {
                String msg = I18nUtil.mapStaticTrans(LoginAccountEnum.class, key, true);
                values.add(msg);
            });
            String split = I18nUtil.mapStaticTrans(PunctuationEnum.class, PunctuationEnum.SEPARATOR.getCode(), true);
            return String.join(split, values);
        };
    }

    /**
     * 单体自己使用 登录获取用户信息service
     *  hosUserMapper 登录获取用户信息mapper
     *  loginModelService 登录方式service
     *  hosSecurityProperties 登录配置
     * @return
     */
    @Bean
    public HosUserDetailsService hosUserDetailsService() {
        return new HosUserDetailsDBService(hosUserMapper, loginModelService, hosSecurityProperties);
    }

    /**
     * 登录完成返回用户默认数据（默认首页等信息）
     * @return
     */
    @Bean
    public HosAppTokenHandler hosAppTokenHandler() {
        return new HosAppTokenHandler();
    }

    // @formatter:off
    @Bean
    @Order(value = Ordered.HIGHEST_PRECEDENCE + 100)
    SecurityFilterChain securityFilterChain(HttpSecurity http, HosSecurityProperties hosSecurityProperties, HosPostInfoService hosPostInfoService) throws Exception {
        // 配置白名单
        List<String> whiteList = SecurityUtil.getWhiteList(hosSecurityProperties.getLogin().getWhiteList());
        List<String> anonymous = new ArrayList<>();
        anonymous.add(backendPath + "/auth-logout");
        anonymous.add(backendPath + "/oauthLogin");
        anonymous.add(backendPath + "/tenant/select-tenant");
        anonymous.add(backendPath + "/token");
        anonymous.add("/druid/**");
        anonymous.add(backendPath + "/auth/**");
        anonymous.add(backendPath + "/social-auth/**");
        anonymous.add(backendPath + "/ca-auth/**");
        anonymous.add(backendPath + "/otp-auth/**");
        anonymous.add(backendPath + "/oauthLogin");
        //oauth
        anonymous.add(backendPath + "/error");
        anonymous.add(backendPath + "/oauth2/smart/authorize");
        ////获取租户的地址
        anonymous.add(backendPath + "/tenant/select-tenant-by-domain");
        anonymous.add(backendPath + "/tenant/select-tenant");
        anonymous.add("/core/system/files/other/**");
        anonymous.add("/core/system/files/image/**");
        anonymous.add("/favicon.ico");
        // knife4j匿名，基于swagger，因此需要添加
        anonymous.add("/v2/api-docs");
        anonymous.add("/swagger-resources");
        ////对外发布的接口 不进行权限校验
        anonymous.add(restfulPath + "/**");
        ///添加白名单的过滤
        for (String url : authProperties.getSkipUrl()) {
            anonymous.add(backendPath + url);
            anonymous.add(contextPath + url);
        }
        http
                .authorizeHttpRequests(authorize ->
                        authorize
                                .antMatchers(whiteList.stream().toArray(String[]::new)).permitAll()
                                .antMatchers(ArrayUtil.toArray(anonymous, String.class)).permitAll()
                                .anyRequest().authenticated()
                );
        // 加载登录配置
        HosLoginConfigurer hosLoginConfigurer = new HosLoginConfigurer();
        // 加载统一认证配置
        if (hosSecurityProperties.getOauth2().isEnable()) {
            hosLoginConfigurer.hosOAuth2Login();
        }
        HosUserDetailsService hosUserDetailsService = hosUserDetailsService();
        HosTokenAuthenticationTokenGenerator hosTokenAuthenticationTokenGenerator= HosConfigurerUtils.getHosTokenAuthenticationTokenGenerator(http);
        hosLoginConfigurer.loginTokenEndpoint(loginTokenEndpoint -> {
            //注入自定义Converter
            loginTokenEndpoint.authenticationConverter(new CallBackAuthenticationConverter());
            loginTokenEndpoint.authenticationConverter(new CasAuthenticationConverter());
            //注入自定义Provider
            loginTokenEndpoint.authenticationProvider(new CallBackAuthenticationProvider(hosUserDetailsService,null,null,hosTokenAuthenticationTokenGenerator));
            loginTokenEndpoint.authenticationProvider(new CasAuthenticationProvider(hosUserDetailsService,null,null,hosTokenAuthenticationTokenGenerator));

        });        http.apply(hosLoginConfigurer);
        // 安全过滤、岗位token验证过滤器，放在登录成功后
        http.addFilterAfter(new HosCoreFilter(), RememberMeAuthenticationFilter.class);
        RequestMatcher ignoreRenewalRequestMatcher;
        List<String> ignoreRenewals = frameworkSecurity.getRenewal().getIgnores();
        if(Objects.nonNull(ignoreRenewals) && !ignoreRenewals.isEmpty()){
            List<RequestMatcher> requestMatchers = ignoreRenewals.stream().map(ignoreRenewal ->new AntPathRequestMatcher(ignoreRenewal, null)).collect(Collectors.toList());
            ignoreRenewalRequestMatcher = new OrRequestMatcher(requestMatchers);
        }else {
            ignoreRenewalRequestMatcher = NoneRequestMatcher.INSTANCE;
        }
        http.addFilterBefore(new HosPostFilter(HosConfigurerUtils.getTokenStore(http), hosPostInfoService, ignoreRenewalRequestMatcher), RememberMeAuthenticationFilter.class);
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //http.csrf(csrf -> csrf.disable());
        http.headers().frameOptions().disable();
        // xss
        if (!frameworkSecurity.getCsrf().isEnable()) {
            http.csrf().disable();
        }
        if (frameworkSecurity.getCors().isEnable()) {
            http.cors();
        }
        if (frameworkSecurity.getXss().isEnable()) {
            // 注册xss过滤器
            XssFilter xssFilter = new XssFilter();
            xssFilter.setEnabled(true);
            List<String> xssExcludes = frameworkSecurity.getXss().getExcludes();
            if (CollectionUtil.isNotEmpty(xssExcludes)) {
                xssFilter.setExcludes(xssExcludes);
            }
            http.addFilterAfter(xssFilter, CsrfFilter.class);
        }
        return http.build();
    }
    // @formatter:on

}
